import pygame
import sys
#导入sys库，用于安全退出程序
import random
import math
import os  # 用于处理文件路径
# 从创建的module包中，导入所有需要用到的类和函数
from module.game_settings import GameSettings
from module.start_screen import draw_start_screen
from module.end_screen import draw_end_screen
from module.snake import Snake, draw_snake_body
from module.ai_snake import AISnake
from module.food import Food
from module.obstacle import generate_dynamic_obstacles, update_dynamic_obstacles, draw_dynamic_obstacles
from module.sound_manager import Sound
from module.score_utils import update_high_score, load_high_score
from module.constant import *
"""*代表导入constant里面全部的内容"""
# 导入所有三种可选的Logo类
from module.cloud_logo import CloudLogo
from module.aurora_logo import AuroraLogo
from module.whale_logo import WhaleLogo

# 全局定义（常量）
SCREEN_X = 800
SCREEN_Y = 800
OBSTACLE_COUNT = 5
OBSTACLE_SIZE = 25
MIN_SPEED = 1
MAX_SPEED = 3

# 界面状态常量
START_STATE = 0 #开始
GAME_STATE = 1  #游戏状态
END_STATE = 2  #结束状态
GRID_SIZE = 25  # 游戏网格大小

# 获取当前文件所在目录的路径，用于之后加载图片和声音文件
BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def show_text(screen, pos, text, color, font_bold=False, font_size=60, font_italic=False):
    # 获取系统字体，并设置文字大小
    cur_font = pygame.font.SysFont("Simsun", font_size)
    # 设置是否加粗属性
    cur_font.set_bold(font_bold)
    # 设置是否斜体属性
    cur_font.set_italic(font_italic)
    # 设置文字内容
    text_fmt = cur_font.render(text, 1, color)
    #antialias  抗锯齿，让文字变得平滑，
    # 在文字颜色和背景颜色的“锯齿”边缘，填充一些介于两者之间的混合色或半透明的像素点。
    # 获取文本的矩形区域
    text_rect = text_fmt.get_rect()
    # 设置文本位置
    text_rect.topleft = pos
    # 绘制文字
    screen.blit(text_fmt, text_rect)
    """screen.blit() 函数的第二个参数（位置参数）可以接受两种类型的值：
    一个坐标元组 (Tuple)，例如 (100, 50)。
    一个矩形对象 (Rect)，例如我们代码中的 text_rect"""
    """1.text_fmt,就是类似一个小的画纸粘贴到大的画纸上面，text_rect,是位置，
    2.在大多数情况下，它只用了这个区域的左上角坐标来定位,screen.blit(text_fmt, text_rect.topleft) 是可以的
    3.一些高级用法中，这个 Rect 还可以用来实现局部绘制（比如，只绘制一个大 Surface 的一小部分到屏幕上）。"""
    return text_rect
    """把 text_rect 这个包含着位置和尺寸信息的矩形对象，作为“成品”交出去,拿到文字在屏幕上的精确位置和大小信息,完成其他操作，
    动态定位，动画效果"""


def run_game_loop(screen, screen_w, screen_h, sound_manager, settings):
    """游戏主循环（返回最终分数和是否主动退出）"""
    #pygame.time.Clock() 就是一个“创建器”，它的作用是创建一个“时钟对象”（Clock Object）。
    clock = pygame.time.Clock()  # 创建一个时钟对象，用来控制游戏的帧率

    # 根据 settings 初始化游戏对象
    # 每次开始一局新游戏，都会在这里创建全新的对象
    player_snake = Snake(BASE_DIR, settings.snake_skin)
    food = Food(BASE_DIR) # 创建一个食物对象

    # 根据在结束/设置界面选择的Logo样式，创建对应的Logo实例
    logo_in_game = None
    if settings.logo_style == 'cloud':
        logo_in_game = CloudLogo(screen_w) # 如果选了云朵，就创建云朵Logo
    elif settings.logo_style == 'aurora':
        logo_in_game = AuroraLogo(120, 60)  # 在游戏内显示在左上角静态位置
    elif settings.logo_style == 'whale':
        your_name = "薛琦"  # 不再需要 "姓名: "
        your_student_id = "0241122033"  # 不再需要 "学号: "
        logo_in_game = WhaleLogo(screen_w, screen_h, name=your_name, student_id=your_student_id)

    ai_snakes = []
    # 仅在AI模式下创建皮肤池和AI蛇
    if settings.game_mode == 'ai':
        # 创建一个AI皮肤池，包含除玩家所选皮肤外的所有皮肤
        ai_skin_pool = list(SNAKE_SKINS.keys())
        if settings.snake_skin in ai_skin_pool:
            ai_skin_pool.remove(settings.snake_skin)
        # 防止池为空的备用方案
        if not ai_skin_pool:
            ai_skin_pool = [key for key in SNAKE_SKINS.keys() if key != 'default']
        """列表推导式，.keys() 是字典的一个方法，它会返回一个包含该字典所有键 (key) 的列表
        1.for循环,依次遍历 SNAKE_SKINS.keys() 返回的列表中的每一个元素。
        2. if 条件判断的 key，它会把这个 key 收集起来，放到一个新创建的列表中。"""
        # 创建两条AI蛇，并从皮肤池中随机选择皮肤
        # 创建AI蛇。
        #    调用 AISnake“蓝图”，从皮肤池(ai_skin_pool)里随机选一个(random.choice)皮肤，
        #    并指定一个固定的出生位置(start_pos)和方向(start_dir)。
        #    .append() 是把创建好的AI蛇对象“添加”到 ai_snakes 列表中。
        ai_snakes.append(AISnake(BASE_DIR, random.choice(ai_skin_pool), screen_w, screen_h, start_pos=(100, 200),
                                 start_dir=pygame.K_RIGHT))
        # 再创建第二条AI蛇。
        ai_snakes.append(AISnake(BASE_DIR, random.choice(ai_skin_pool), screen_w, screen_h, start_pos=(700, 600),
                                 start_dir=pygame.K_LEFT))

    # 合并所有初始蛇的身体用于生成障碍物
    # 把玩家的蛇身(player_snake.body)和所有AI的蛇身(ai_snakes)合并成一个大列表。
    # 那个 [body for ai in ai_snakes for body in ai.body] 也是列表推导式，
    # 意思是“遍历所有ai蛇，再遍历ai蛇的每一节身体，把所有身体都收集到一个新列表里”。
    initial_bodies = player_snake.body + [body for ai in ai_snakes for body in ai.body]
    # 调用我们从 obstacle.py 导入的函数，来生成障碍物。
    # 我们把障碍物数量、大小、速度、以及刚才合并的“所有蛇身”列表传进去，
    # 这样函数就知道生成障碍物时要避开蛇
    obstacles = generate_dynamic_obstacles(
        OBSTACLE_COUNT, OBSTACLE_SIZE, SCREEN_X, SCREEN_Y,
        MIN_SPEED, MAX_SPEED, initial_bodies, food.normal_rect
    )

    # 游戏状态变量
    # 调整不同模式的起始分数
    # 这是一个“三元运算符”，是 if/else 的简化写法。
    # 意思是：如果模式是'endless'，分数(scores)就从500开始，否则(else)就从0开始。
    scores = 500 if settings.game_mode == 'endless' else 0
    # 这是一个“布尔”标志位，False代表玩家还活着
    isdead = False
    # 准备一个空字符串，用来存放玩家的“死因
    death_reason = ""
    # 初始化碰撞计数器（撞墙和撞障碍物次数）
    wallsum = 0
    # 初始化撞障碍物次数为 0
    obstaclesum = 0

    # 特殊食物状态管理变量，False代表当前没有特殊食物在生成
    special_food_spawning = False  # 特殊食物是否正在生成（防止重复生成）

    # 为玩家和AI蛇设置独立的“下一个增长分数”阈值
    # 在AI模式下，玩家需要达到200分才能长一节
    player_next_growth_score = 200
    # 为每个AI也设置一个200分的目标。
    # [200 for _ in ai_snakes] 的意思是：ai_snakes列表里有几个AI，我就创建几个200。
    # 比如有2个AI，这个列表就是 [200, 200]。
    ai_next_growth_scores = [200 for _ in ai_snakes]  # 为每个AI蛇创建一个阈值

    # 新增4个变量，用于控制过渡动画
    is_transitioning = False  # 是否正在播放过渡动画，False代表没有
    transition_progress = 0.0  # 过渡动画的进度 (0.0 到 1.0)
    TRANSITION_DURATION = 150  # 过渡动画的总帧数 (值越大，动画越慢)150帧在60FPS下大约是2.5秒。
    screen_capture = None  # 用于保存游戏画面的截图

    # 新增方格溶解和粒子系统所需的变量
    # 存放所有“爆炸”粒子的列表。
    particles = []
    # 存放所有“已经变黑”的格子坐标的集合。
    dissolved_cells = set()
    # 存放所有格子坐标的列表，这个列表会被排序
    cell_order = []

    # 难度阶段配置
    # 修改新内容1：定义“得分区间、障碍物速度、背景色”三个列表（一一对应）
    # score_ranges：得分上限（0~500为阶段1，501~1000为阶段2，501+为阶段3）
    score_ranges = [200, 500, float('inf')]
    # obstacle_speeds：对应阶段的障碍物移动速度（得分越高，速度越快）
    obstacle_speeds = [1.0, 1.8, 5]

    #定义所有背景颜色主题的调色板
    # 这是一个“字典”，它把“主题名字”(key)和“颜色列表”(value)关联起来。
    # 'grey' 主题的三个阶段颜色是 (95,95,95), (120,120,120), (150,150,150)。
    color_themes = {
        'grey': [(95, 95, 95), (120, 120, 120), (150, 150, 150)],#对应阶段的背景色（从深灰→中灰→浅灰，视觉反馈难度升级）
        # 一种带有灰度的、柔和的绿色，有明显的明暗变化但整体不刺眼
        'green': [(180, 215, 200), (200, 230, 215), (220, 240, 230)],

        # 清新黄 (Lemon Cream / 柠檬奶油色)
        # 一种非常淡的、偏奶油质感的黄色，避免了高饱和度带来的冲突
        'yellow': [(255, 245, 200), (255, 248, 215), (255, 252, 230)]
    }
    # 根据玩家在设置中选择的主题，来决定当前游戏使用的背景颜色列表
    # 从“设置”里获取玩家选择的主题名字（比如'green'）。
    # .get() 是字典的安全获取方法。它会尝试获取 settings.background_color，
    # 如果没找到（比如玩家没选），它会使用 'grey' 作为默认值。
    # 所以 bg_colors 最终会得到一个颜色列表，比如 'green' 对应的那个。
    bg_colors = color_themes.get(settings.background_color, color_themes['grey'])
    """
    1..get(Key, Default_Value) (核心函数)
    .get() 是字典的一个“安全获取”方法。它永远不会导致程序崩溃。
    2.第一个参数 (Key): settings.background_color    这是“你想找的键”。
    settings.background_color 是一个变量，它存储了用户当前希望使用的主题名字（比如是一个字符串 'blue'）。
    第二个参数 (Default_Value): color_themes['grey']
    这是“如果找不到，就用这个”的默认值。
    这一小块 color_themes['grey'] 的意思是：“先从菜单里把‘灰色主题’的颜色拿出来，准备好。”
    """
    # 为新的背景色系搭配更和谐的网格线颜色
    grid_line_colors = {
        'grey': (80, 80, 80),
        'green': (158, 188, 173),  # 搭配一个更深的、和谐的鼠尾草绿
        'yellow': (225, 215, 180)  # 搭配一个柔和的、较深的米黄色

    }
    # 获取当前主题应该使用的网格线颜色。
    current_grid_color = grid_line_colors.get(settings.background_color, grid_line_colors['grey'])


    def trigger_special_effect(food_type,snake_who_ate_it):
        """触发特殊食物的特效"""
        """
        触发特殊食物的特效。
        :param food_type: 食物类型 (1, 2, 3)
        :param snake_who_ate_it:吃到食物的那个蛇对象 (可以是 player_snake 或 ai_snake)
        :return: 本次操作应该增加的分数
        """
        # "nonlocal" 是一个关键字，它告诉这个内部函数：
        # 当我使用 "obstacles" 这个变量时，我指的不是“我自己”的局部变量，
        # 而是“外面那层函数”(run_game_loop)的 "obstacles" 变量。
        nonlocal obstacles  # 声明使用外部作用域的变量
        # 初始化本次吃食物的奖励分数为 0。
        score_bonus = 0

        # 根据传入的食物类型（1, 2, 或 3）来执行不同的逻辑。
        if food_type == 1:
            # 把玩家的速度倍率设为 1.2。
            player_snake.speed_multiplier = 1.2
            # 奖励 150 分
            score_bonus = 150
            #修改标记 1：根据蛇的类型播放不同音效
            # --- 检查是谁吃了食物 ---
            # hasattr(obj, 'attr') 是一个检查函数，它会检查 "snake_who_ate_it" 这个对象
            # 是否“拥有一个叫 'is_ai' 的属性”。
            # AI蛇有这个属性，玩家蛇没有。
            if hasattr(snake_who_ate_it, 'is_ai') and snake_who_ate_it.is_ai:
                # 如果是AI吃的，就播放AI吃东西的音效。
                sound_manager.play_sound("ai_eating")  # 如果是AI，播放ai_eating
            else:
                sound_manager.play_sound("speed_up")  # 如果是玩家，播放原音效

        elif food_type == 2:
            # 类型2：缩短
            # 计算目标长度：当前长度的一半，但最少不低于 3。
            target_length = max(3, len(player_snake.body) // 2)
            # 循环，只要蛇还比目标长，就一直调用 delnode() 来删除蛇尾
            while len(player_snake.body) > target_length: player_snake.delnode()
            score_bonus = 200
            ## --- 修改标记 2：根据蛇的类型播放不同音效 --- ##
            if hasattr(snake_who_ate_it, 'is_ai') and snake_who_ate_it.is_ai:
                sound_manager.play_sound("ai_eating")  # 如果是AI，播放ai_eating
            else:
                sound_manager.play_sound("shorten_body")  # 如果是玩家，播放原音效


        elif food_type == 3:
            # 类型3：清屏
            # 计算要清除的数量：最多 3 个，或者场上所有障碍物（取较小值）
            clear_count = min(3, len(obstacles))
            for _ in range(clear_count):
                # 循环 clear_count 次
                if obstacles: obstacles.pop()
                # .pop() 会移除列表的最后一个元素
            # 重新获取所有蛇的身体。
            all_bodies = player_snake.body + [b for ai in ai_snakes for b in ai.body]
            """
            重新获取身体：all_bodies = player_snake.body + ...
            1.generate_dynamic_obstacles (生成动态障碍物) 这个函数，
            它在创建新障碍物时，必须知道 all_bodies（所有蛇的身体）在哪里。
            
            """
            # 重新生成被清除掉的同等数量的新障碍物
            new_obstacles = generate_dynamic_obstacles(
                clear_count, OBSTACLE_SIZE, SCREEN_X, SCREEN_Y,
                MIN_SPEED, MAX_SPEED, all_bodies, food.normal_rect
            )
            # 把新生成的障碍物列表“扩展”到原有的列表中。
            obstacles.extend(new_obstacles)
            score_bonus = 250
            ## --- 修改标记 3：根据蛇的类型播放不同音效 --- ##
            if hasattr(snake_who_ate_it, 'is_ai') and snake_who_ate_it.is_ai:
                sound_manager.play_sound("ai_eating")  # 如果是AI，播放ai_eating
            else:
                sound_manager.play_sound("clear_obstacles")  # 如果是玩家，播放原音效
        # 把计算好的奖励分数返回
        return score_bonus

    # 初始化普通食物（游戏开始时就有一个普通食物）
    food.set_normal(SCREEN_X, initial_bodies, obstacles)

    # ==================== 主循环开始 ====================
    # 设置一个标志位，代表游戏“正在运行”。
    running = True
    # 只要 running 标志位是 True，这个循环就会一直重复执行。这就是游戏的核心！
    # 每一轮循环，就是游戏的一“帧”（Frame）。
    while running:
        # 1. 事件处理 (Event Handling)
        # 检查玩家在这一帧里做了什么操作。

        # pygame.event.get() 会返回一个列表，里面装着这一瞬间发生的所有事件（按键、鼠标等）。
        # for 循环会遍历这个列表中的每一个事件。
        for event in pygame.event.get():
            # 如果事件的类型是“点击了窗口的X关闭按钮”
            if event.type == pygame.QUIT:
                # 调用 pygame 的退出命令。
                pygame.quit()
                # 调用 sys 的退出命令，彻底关闭程序。
                sys.exit()
            # 如果事件的类型是我们之前在 Sound 里设置的“自定义事件”（音乐播放完毕）。
            if event.type == pygame.USEREVENT + 1:
                """
                1.pygame.USEREVENT 是 Pygame 系统预留的一个“数字”，专门用来给你（User/用户）创建自己的事件
                2.pygame.USEREVENT + 1, pygame.USEREVENT + 2, pygame.USEREVENT + 3... 来代表各种不同的自定义事件
                3.pygame.mixer.music.set_endevent(...) 是什么？这是 Pygame 的背景音乐播放器的一个功能。
                set_endevent 的意思是：“请设置一个‘结束事件
                """
                # 就让音乐重新播放。
                sound_manager.resume_background_music()
            # 修改键盘事件处理，让 Backspace 触发过渡动画
            if event.type == pygame.KEYDOWN:
                # 首先检查，是不是正在播放“退出”动画
                if not is_transitioning:
                    # 游戏进行中（无论死活）按 Backspace 键都会触发过渡动画
                    # 如果不是在播动画...
                    # 检查按下的键是不是“退格键”(Backspace)
                    if event.key == pygame.K_BACKSPACE:
                        # --- 触发“退出”动画 ---
                        # 1. 把“正在过渡”的标志位设为 True。
                        is_transitioning = True
                        # 2. 立刻截取当前屏幕的画面，存起来
                        screen_capture = screen.copy()
                        # 3. 初始化溶解动画所需的变量。
                        # (过渡动画的初始化代码不变)
                        particles, dissolved_cells, cell_order = [], set(), []
                        # 4. 计算屏幕中心的格子坐标
                        center_x, center_y = screen_w // (2 * GRID_SIZE), screen_h // (2 * GRID_SIZE)
                        """
                        “先算出总共有多少列格子，再除以 2，找到最中间那一列的索引（编号）
                        1.第 1 步：先算出一共有多少列（Column）？  total_columns = screen_w // GRID_SIZE
                        （假设 screen_w 是 800，GRID_SIZE 是 25，那么 800 // 25 = 32。意思是屏幕上一共有 32 个格子宽。）
                        第 2 步：找出“中间”那一列的编号是多少？  center_x = total_columns // 2
                        （32 // 2 = 16。意思是，第 16 号格子就是最中间的那一列。）
                        你代码里的 screen_w // (2 * GRID_SIZE) 只是把这两步合并成了一步： 800 // (2 * 25) = 800 // 50 = 16。
                        2.同理， screen_h // (2 * GRID_SIZE)是找中间的那行的索引
                        """
                        # 5. 遍历所有格子，计算它们到中心点的距离。
                        for r in range(screen_h // GRID_SIZE):
                            for c in range(screen_w // GRID_SIZE):
                                cell_order.append(((c, r), math.sqrt((c - center_x) ** 2 + (r - center_y) ** 2)))
                        # 6. 关键：把所有格子按照“离中心点的距离”从小到大排序。
                        #    这样溶解效果就会从中心向外扩散。
                        cell_order.sort(key=lambda item: item[1])
                        """
                         cell_order.sort(key=lambda item: item[1]):按照每个元素（“小包裹”）的第 2 个部分**（也就是“距离”），从小到大排列
                         1..sort(): 这是 Python 列表自带的一个标准方法。它的功能就是“原地排序”（执行后，cell_order 列表本身的顺序就被永久改变了)
                         2.lambda 是 Python 的一个关键字，用来创建一个“临时的、匿名的、小型的”函数
                         item：这是一个“代号”。它代表 .sort() 在排序时，从 cell_order 列表里“随便拿出来”的一个元素（一个“小包裹”）
                         3.item[1]:这是这个“临时小函数”的“返回值”。
                         item[1] 的意思是：“请把 item（这个小包裹）的第 2 个部分（索引为 1）拿出来
                         4.结论： 所以，你给 sort 函数的“规则” key=lambda item: item[1]，就是在告诉它： “排序时，请你只看每个‘小包裹’里的那个‘距离’值！
                        """
                    # 只有在死亡后，按空格键才有效
                    # 如果按下的不是退格键，再检查：玩家是不是已经死了？按的是不是空格键？
                    elif isdead and event.key == pygame.K_SPACE:
                        # 如果是，就"return"（返回）一个信号 "restart"。
                        # "return" 会立刻跳出这个 run_game_loop 函数。
                        return "restart"
                    # 如果玩家还活着
                    # 只有在活着的时候，方向键才有效
                    elif not isdead:
                        # 就把按键 (event.key) 传给蛇的 changedirection 方法，让蛇尝试转向。
                        player_snake.changedirection(event.key)

        # 2. 逻辑更新 (Logic Update)
        # 这一部分负责计算所有游戏对象（蛇、食物、AI）的下一步状态
        # 这是一个“总开关”：只有在“不在播退出动画”并且“玩家还活着”的情况下，才执行游戏的核心逻辑。
        if not is_transitioning and not isdead:
            # 只有在活着的时候才更新所有游戏状态
            # 分数增长机制不变
            scores += 1
            for ai in ai_snakes:
                ai.score += 1

            # 告诉玩家的蛇对象，调用你自己的 move() 方法，向前走一步
            player_snake.move()
            # 告诉食物对象，调用你自己的 update() 方法（用来更新倒计时和动画）。
            food.update()
            # 如果 logo_in_game 不是 None（即玩家选择了Logo）
            if logo_in_game:
                # 告诉 Logo 对象，调用你自己的 update() 方法（用来更新动画）。
                logo_in_game.update()

            ## --- 修改标记 1：将两种食物的位置都传递给AI --- ##
            # 遍历AI蛇列表，(enumerate 会同时给你索引 idx 和 蛇ai_snake)。
            for idx, ai_snake in enumerate(ai_snakes):
                # 准备AI决策所需的信息：一个包含了“玩家蛇身”和“另一条AI蛇身”的大列表。
                other_bodies = player_snake.body + [s.body for i, s in enumerate(ai_snakes) if i != idx][0]
                """
                列表推导式，enumerate(ai_snakes): 遍历所有的 AI 蛇，同时给出索引 i (第几条蛇) 和蛇对象 s,并且i不等于idx,
                1.s.body: 对于所有“其他的蛇”，把它的 body（身体列表）拿出来。结果: 这一步会创建出一个列表的列表
                2....[0] [0]: 这是在对上一步得到的“列表的列表”取第一个元素。
                举例：[ [蛇B的身体列表], [蛇C的身体列表] ][0] 的结果是 [蛇B的身体列表]。
                3.最终的 + (拼接)  player_snake.body + [蛇B的身体列表]
                结果：other_bodies 最终会变成一个**“畸形”**的列表，它里面装着玩家的身体方块（Rects），以及一个“列表”。
                例如：[玩家方块1, 玩家方块2, [蛇B方块1, 蛇B方块2]]
                """
                # 将 food.normal_rect 和 food.special_rect 都作为参数传入
                # 调用AI蛇的“大脑”：update_direction()。
                # 把普通食物、特殊食物、障碍物、玩家、其他蛇的位置全都告诉它。
                ai_snake.update_direction(food.normal_rect, food.special_rect, obstacles, player_snake.body, other_bodies)
                # 告诉AI蛇，根据你刚才的决策，走一步
                ai_snake.move()
                # 获取AI蛇的头部
                ai_head = ai_snake.body[0]
                # 检查AI蛇是否撞到了障碍物。
                for obs in obstacles:
                    if ai_head.colliderect(obs["rect"]):
                        # AI撞到障碍物的惩罚：如果蛇身够长，就缩短两节。
                        if len(ai_snake.body) > 3:
                            ai_snake.delnode()
                            ai_snake.delnode()
                        # 把这个障碍物重置到屏幕顶端
                        obs["rect"].y = -obs["rect"].height
                        # 跳出障碍物循环（因为一次只处理一个碰撞）
                        break

            # 人机模式下，根据分数增长蛇长
            if settings.game_mode == 'ai':
                # 检查玩家分数是否达到增长阈值
                if scores >= player_next_growth_score:
                    # 如果达到了，玩家蛇长一节
                    player_snake.addnode()
                    # 并且把下一个目标分数提高 200 分。
                    player_next_growth_score += 200  # 更新下一个目标分数
                # 检查每个AI蛇的分数是否达到增长阈值
                for i, ai_snake in enumerate(ai_snakes):
                    # 检查这个AI的分数是否达到了它自己的增长目标。
                    if ai_snake.score >= ai_next_growth_scores[i]:
                        # 如果达到了，AI蛇长一节。
                        ai_snake.addnode()
                        # 并且把这个AI的下一个目标分数提高 200 分。
                        ai_next_growth_scores[i] += 200  # 更新此AI的下一个目标分数

            # 食物碰撞检测
            # 检查玩家的蛇头 (body[0]) 是否与普通食物的矩形 (normal_rect) 发生了碰撞。
            if player_snake.body[0].colliderect(food.normal_rect):
                # 奖励 50 分。
                scores += 50
                # 播放吃东西的音效
                sound_manager.play_sound("food_eat")
                food.remove_normal()
                # 在非人机模式下，吃食物直接增长
                if settings.game_mode != 'ai':
                    player_snake.addnode()

            # 遍历所有AI蛇，检查它们是否吃到了普通食物
            for ai_snake in ai_snakes:
                # 检查：1. 食物是否还在 (x != -25) 2. AI蛇头是否碰到了食物。
                if food.normal_rect.x != -25 and ai_snake.body[0].colliderect(food.normal_rect):
                    # AI蛇加分。
                    ai_snake.score += 50
                    # 播放AI吃东西的音效
                    sound_manager.play_sound("ai_eating")
                    # 移除食物。
                    food.remove_normal()
                    # 跳出循环，因为食物已经被吃了，没必要再检查其他AI了
                    break

            # 检查玩家是否吃到了特殊食物
            if food.is_special_active and player_snake.body[0].colliderect(food.special_rect):
                # 调用我们之前定义的“特效”函数，把玩家自己(player_snake)传进去。
                score_bonus = trigger_special_effect(food.special_type, player_snake)
                # 把返回的奖励分数加到总分上
                scores += score_bonus
                # 移除特殊食物
                food.remove_special()

            #新增AI吃特殊食物的逻辑
            # 循环检测每个AI蛇，如果吃到就施加效果并跳出循环
            # 只有在食物还存在时才进行检测
            if food.is_special_active:
                # 遍历所有AI蛇。
                for ai_snake in ai_snakes:
                    # 检查AI蛇头是否碰到了特殊食物。
                    if ai_snake.body[0].colliderect(food.special_rect):
                        # AI吃到了！
                        # 调用“特效”函数，但这次把AI蛇(ai_snake)传进去。
                        score_bonus = trigger_special_effect(food.special_type, ai_snake)
                        ai_snake.score += score_bonus  # 将分数奖励加给这个AI
                        food.remove_special()  # 移除食物
                        break  # 食物已被吃，无需再检测其他AI，跳出循环

            # 确保普通食物始终存在
            # 检查普通食物是否被吃了（x坐标为-25）
            if food.normal_rect.x == -25:
                # 重新获取场上所有蛇的身体
                all_bodies = player_snake.body + [b for ai in ai_snakes for b in ai.body]
                # 告诉食物对象，生成一个新的普通食物，并避开 all_bodies。
                food.set_normal(SCREEN_X, all_bodies, obstacles)

            # 特殊食物生成逻辑
            if not food.is_special_active and not special_food_spawning:
                # （这部分逻辑是根据分数来查表，决定一个生成概率）
                current_prob = 0.01
                for score_limit, prob in zip(*zip(*SPECIAL_FOOD_CONFIG)):
                    """
                    1.for 循环的目的，就是想把 score_limit 设为 100，prob 设为 0.01；
                    下一轮，score_limit 设为 1000，prob 设为 0.1
                    2.zip()：是“拉链”的意思，它会把几个列表“合并”成一对一的元组。
                    * (星号)：是“解包”的意思，它会把一个列表“拆散”成独立的元素。
                    zip(*SPECIAL_FOOD_CONFIG) (第一次 zip)：
                    这会“解包” CONFIG 列表，变成：(100, 0.01), (1000, 0.1), (2000, 0.2)
                    然后 zip 它们，zip((100, 0.01), (1000, 0.1), ...)
                    结果是“矩阵转置”：它会返回 (100, 1000, 1000) 和 (0.01, 0.1, 0.25)
                    3.zip(*zip(...)) (第二次 zip)：
                    它现在在 zip 上一步的结果，也就是 zip((100, 1000, 1000), (0.01, 0.1, 0.1))
                    结果是“转置回来”：它又返回了 (100, 0.01), (1000, 0.1), (2000, 0.2)。
                    """
                    if scores <= score_limit: current_prob = prob; break
                # 掷骰子：生成一个0.0到1.0之间的随机数。
                if random.random() < current_prob:
                    """在 Python 中实现“概率”或“抽奖”的标准方法
                    “人话”翻译是：“我们来‘掷骰子’，看看‘中奖’了没有
                    1.random.random() (掷骰子) 这是一个 random（随机）模块里的函数。
                    它的唯一功能：返回一个“随机的浮点数（小数）”，这个数字必定在 0.0 (包含) 和 1.0 (不包含) 之间。
                    你可以把它想象成一个“百分比骰子”。它可能会掷出 0.123 (12.3%), 0.8 (80%), 0.999 (99.9%) 或 0.001 (0.1%)
                    2.current_prob (中奖线)    这是你上一行代码算出来的“中奖概率”。
                    假设玩家分数很低，current_prob 的值是 0.01 (也就是 1%
                    3.if ... < ... (判断是否中奖) if random.random() < 0.01:
                    这行代码在问：“我刚刚随机掷出的那个 0.0到1.0 之间的数字，是不是小于 0.01 ？”
                    思考一下： 一个 0.0 到 1.0 之间的随机数，它落在 0.0 到 0.01 这个“小区间”的概率有多大？
                    答案是：正好是 1%！
                    所以，这个 if 语句有 1% 的概率会成功 (True)，有 99% 的概率会失败 (False)。
                    """
                    # 如果小于概率（即“中奖”），就生成特殊食物
                    special_type = random.choice([1, 2, 3])
                    all_bodies = player_snake.body + [b for ai in ai_snakes for b in ai.body]
                    food.set_special(SCREEN_X, special_type, all_bodies, obstacles)
                    special_food_spawning = True    # 设置标志位，防止下一帧又生成。
            # 如果食物被吃了，就重置标志位，允许下一次生成
            if not food.is_special_active and special_food_spawning:
                special_food_spawning = False

            # <--- 【新增代码】 --->
            # --- 鲸鱼 Logo 与玩家蛇头碰撞检测 ---
            # 检查 logo_in_game 是否存在 并且 是 WhaleLogo 类型
            # isinstance(object, classinfo) 检查一个对象是不是某个类的实例
            if logo_in_game and isinstance(logo_in_game, WhaleLogo):
                # 获取玩家蛇头
                head = player_snake.body[0]
                # 从 WhaleLogo 对象获取当前帧计算好的眼睛碰撞区域
                eye_rect = logo_in_game.eye_collision_rect
                # 必须检查 eye_rect 是否存在（因为第一帧可能还没计算出来）
                if eye_rect:
                    # 使用矩形碰撞检测方法 colliderect()
                    if head.colliderect(eye_rect):
                        # 如果碰撞了，就调用 WhaleLogo 的方法触发闭眼
                        logo_in_game.trigger_eye_close()  # 默认闭眼5秒
            # <--- 【新增代码】结束 --->


            # 死亡检测与处理
            # 获取玩家蛇头的矩形。
            head = player_snake.body[0]
            # 这是一个“lambda（匿名）函数”，它是一个微型函数。
            # 它的功能是：接收一个参数 n，并返回 n + 1。
            # 所以 countsum(wallsum) 就会返回 wallsum + 1。
            """lambda表达式实现计数功能，撞墙wallsum+1，撞障碍物obstaclesum+1"""
            countsum = lambda n: n + 1

            # 检测撞墙
            if head.x not in range(SCREEN_X) or head.y not in range(SCREEN_Y):
                # 如果撞墙了：
                # 1. 设置死亡标志位为 True。
                isdead, death_reason = True, "撞墙了！"
                # 2. 播放撞墙音效
                sound_manager.play_sound("wall_death")
                # 3. 撞墙次数+1。
                wallsum = countsum(wallsum)
                # 4. 在控制台打印信息（用于调试）。
                print(f"撞墙次数：{wallsum}")

            # 检测碰撞障碍物
            # 检查是否撞障碍物 (前提是还没因撞墙而死)。
            if not isdead:
                """map和lambda检测蛇头是否与任何障碍物发生碰撞,
                1.colliderect 是pygame的矩形检测方法，返回True表示碰撞，返回False表示没碰撞
                2.obstacles：存储所有障碍物的列表，每个元素是字典
                3.lambda 表达式，接收一个障碍物obs作为参数，返回该障碍物是否与蛇头碰撞的结果（True或False
                4.map 遍历obstacles中的每个障碍物，把每个障碍物传给前面的lambda函数，得到一系列True/False的结果（形成一个迭代器)
                5.list,把map返回的迭代器转换成列表，最终得到一个由True/False组成的列表"""
                collisions = list(map(lambda obs: head.colliderect(obs["rect"]), obstacles))
                # 检查这个结果列表里是否包含 True。
                if True in collisions:
                    # 如果是 'endless' (无尽) 模式。
                    if settings.game_mode == 'endless':
                        # 撞到不死，而是扣100分
                        scores -= 100
                        obstaclesum = countsum(obstaclesum)
                        # 如果分数被扣光了。
                        if scores <= 0:
                            # 游戏结束。
                            scores, isdead, death_reason = 0, True, "分数耗尽！"
                            sound_manager.play_sound("obstacle_hit")
                            print(f"障碍物次数：{obstaclesum}")
                    else:
                        # 如果是 'simple' 或 'ai' 模式。
                        # 撞到就直接死
                        isdead, death_reason = True, "撞到障碍物了！"
                        sound_manager.play_sound("obstacle_hit")
                        obstaclesum = countsum(obstaclesum)
                        print(f"障碍物次数：{obstaclesum}")

            # AI vs 玩家碰撞检测
            if not isdead and settings.game_mode == 'ai':
                # 遍历每一个AI蛇
                for i, ai_snake in enumerate(ai_snakes):
                    # 检查：玩家的蛇头是否碰到了AI蛇身体的任何一节
                    if head in ai_snake.body:
                        # 如果玩家比AI长（或一样长)
                        if len(player_snake.body) >= len(ai_snake.body):
                            # 将AI的全部分数加给玩家
                            # 玩家“吃掉”AI！
                            # 把AI的所有分数都加给玩家
                            scores += ai_snake.score
                            # 将AI的全部长度加给玩家
                            for _ in range(len(ai_snake.body)):
                                player_snake.addnode()

                            # AI重生，分数会自动清零
                            new_ai_skin = random.choice(ai_skin_pool)
                            # 调用AI的reset方法。
                            ai_snake.reset(skin_id=new_ai_skin)
                            # 重置此AI的增长分数阈值
                            ai_next_growth_scores[i] = 200

                        else:
                            # 如果玩家比AI短。
                            # 玩家死亡。
                            isdead, death_reason = True, "被AI吞并了！"
                            sound_manager.play_sound("wall_death")
                            # 跳出AI循环。
                            break

                    # 检查：AI的蛇头是否碰到了玩家的身体，并且AI比玩家长。
                    if ai_snake.body[0] in player_snake.body and len(ai_snake.body) > len(player_snake.body):
                        isdead, death_reason = True, "被AI吞并了！"
                        sound_manager.play_sound("wall_death")
                        # 跳出AI循环。
                        break

        # 3. 画面绘制 (Drawing)
        # 新增一个总的 if/else 结构来区分“播放动画”和“正常游戏”
        if is_transitioning:
            # 增加动画进度，(1 / 150) 确保它在 150 帧后达到 1.0
            transition_progress += 1 / TRANSITION_DURATION
            # 限制进度最大为 1.0
            if transition_progress > 1.0: transition_progress = 1.0
            # 先把我们之前截的图画上去，作为背景
            screen.blit(screen_capture, (0, 0))
            # 根据当前的进度(百分比)，计算出“应该”有多少格子变黑了
            num_to_dissolve = int(len(cell_order) * transition_progress)
            # 找出这一帧“新”变黑的格子有哪些
            newly_dissolved_this_frame = [cell_order[i][0] for i in range(len(dissolved_cells), num_to_dissolve) if
                                          i < len(cell_order)]
            """
            len(dissolved_cells)：是“上一帧总共有多少个格子已经黑了”。
            num_to_dissolve：是“这一帧应该总共有多少个格子是黑的”。
            这个 range 就代表了**“从旧数量到新数量”之间的“差额”，也就是“新增”的那些格子的索引**。
            cell_order[i][0]: 对每一个“新增”的索引 i，去 cell_order 里把它对应的**“格子坐标” (c, r)** 拿出来
            1.cell_order 是一个排好序的列表，里面的每一个元素（我们叫它“小包裹”）都是这个样子的： ( (格子的坐标), 它离中心的距离 ) 例如：( (16, 16), 0.0 )
            2.cell_order[i]:这是在 for 循环中，从 cell_order 列表里取出第 i 个“小包裹”。
            比如，当 i = 0 时，它取出的就是 ( (16, 16), 0.0 )。
            3....[0]:这是在对刚刚取出的那个“小包裹” ( (16, 16), 0.0 )，再取出它的第 1 个部分（索引为 0）。
            在这个“小包裹”里：第 1 部分 [0] 是：格子的坐标 (16, 16)。第 2 部分 [1] 是：距离 0.0
            4.cell_order[i][0] 的完整意思就是：“从排好序的 cell_order 列表里，取出第 i 个元素，并且只拿走它的‘坐标’部分，忽略掉‘距离’部分
            """
            # 把这些新变黑的格子加入到“已变黑”的集合中
            dissolved_cells.update(newly_dissolved_this_frame)
            # 解释：注意！这里**只遍历**第 4 步找到的“**新**黑格”列表
            # 把所有“已变黑”的格子都涂上黑色
            for c, r in dissolved_cells: pygame.draw.rect(screen, (0, 0, 0),
                                                          (c * GRID_SIZE, r * GRID_SIZE, GRID_SIZE, GRID_SIZE))

            """
            元组解包，dissolved_cells（一个集合 Set），newly_dissolved_this_frame（一个列表 List）
            里面装的都是**“格子的坐标元组”**，比如 (10, 5)、(16, 16)
            for ... in ...: 这个循环会依次从集合或列表里拿出一个元素。
            比如，第一次它拿出了元组 (10, 5)。
            for c, r in ...: 这个语法是在告诉 Python：
            “我知道我拿出来的这个元素 (10, 5) 是一个正好包含两个值的元组。”
            “请你自动地把第一个值（10）放进变量 c 里。”
            “请你自动地把第二个值（5）放进变量 r 里。
            """
            # --- 粒子效果 ---
            # 遍历所有“新”变黑的格子。
            for c, r in newly_dissolved_this_frame:
                # 计算这个格子的中心像素坐标。
                px, py = (c + 0.5) * GRID_SIZE, (r + 0.5) * GRID_SIZE
                # 在这个位置生成 10 个粒子，给它们随机的速度、生命等
                for _ in range(10): particles.append(
                    {'pos': pygame.Vector2(px + random.uniform(-5, 5), py + random.uniform(-5, 5)),
                     'vel': pygame.Vector2(random.uniform(-2, 2), random.uniform(-2, 2)),
                     'life': random.uniform(1.0, 1.8), 'max_life': 1.8,
                     'color': random.choice([(0, 200, 255), (150, 0, 200), (255, 255, 255)])})
            # 计算屏幕中心点。
            center_pos = pygame.Vector2(screen_w / 2, screen_h / 2)
            # 遍历所有粒子 (用 [:] 遍历副本，这样才能在循环中安全删除)
            for p in particles[:]:
                # 粒子生命值减少。
                p['life'] -= 1 / 60.0
                # 如果生命结束，就从列表中移除。
                if p['life'] <= 0: particles.remove(p); continue
                # 施加一个“引力”，让粒子飞向屏幕中心。
                p['vel'] += (center_pos - p['pos']).normalize() * 0.4
                # 施加一个“引力”，让粒子飞向屏幕中心。
                p['pos'] += p['vel']
                # 根据剩余生命计算透明度。
                alpha = int(255 * (p['life'] / p['max_life']))
                # 创建一个超小的临时画布。
                temp_surf = pygame.Surface((6, 6), pygame.SRCALPHA)
                # 在小画布上画一个半透明的圆
                pygame.draw.circle(temp_surf, (*p['color'], alpha), (3, 3), 2)
                # 用“发光”模式把这个小画布“贴”到主屏幕上。
                screen.blit(temp_surf, (p['pos'].x - 3, p['pos'].y - 3), special_flags=pygame.BLEND_RGBA_ADD)

            # 检查动画是否结束：进度条满了 并且 粒子都消失了
            if transition_progress >= 1.0 and not particles:
                # 如果都结束了，就把 running 设为 False。
                # 这将导致 `while running:` 循环在下一轮停止。
                running = False
        else:
            # 绘制背景和网格
            """zip函数用来和得分，障碍物速度，背景色进行绑定"""
            # zip将“得分上限、障碍物速度、背景色”三者绑定，循环匹配当前得分
            # 先设置一个默认值（阶段1）
            current_obstacle_speed, current_bg_color = 1.0, bg_colors[0]    # 默认使用第一阶段颜色
            # 遍历这几组“绑定”好的配置。
            for score_limit, obs_speed, bg_color in zip(score_ranges, obstacle_speeds, bg_colors):
                # 检查玩家当前分数是否“小于等于”这个阶段的上限。
                if scores <= score_limit:
                    # 如果是，说明我们找到了玩家所在的阶段。
                    # 保存这个阶段的速度和背景色。
                    current_obstacle_speed, current_bg_color = obs_speed, bg_color
                break

            # 按匹配到的配置更新背景色
            # 使用我们刚刚动态找到的颜色，填充整个屏幕的背景
            screen.fill(current_bg_color)  # 原固定(95,95,95)，现在动态变化

            # 使用动态变量绘制网格线
            for x in range(0, SCREEN_X, 25):
                pygame.draw.line(screen, current_grid_color, (x, 0), (x, SCREEN_Y), 1)
            for y in range(0, SCREEN_Y, 25):
                pygame.draw.line(screen, current_grid_color, (0, y), (SCREEN_X, y), 1)

            # 绘制游戏元素
            # 如果Logo存在，就画Logo。
            if logo_in_game: logo_in_game.draw(screen)
            # 根据当前阶段的速度，更新所有障碍物的速度。
            # (这个 if/else 是为了保持障碍物的移动方向不变，只改变速率)。
            for obs in obstacles: obs["speed_y"] = current_obstacle_speed if obs["speed_y"] > 0 else -current_obstacle_speed
            # 调用函数，更新障碍物的位置。
            update_dynamic_obstacles(obstacles, SCREEN_Y)
            # 调用函数，绘制障碍物。
            draw_dynamic_obstacles(screen, obstacles)
            # 告诉食物对象，画出你自己。
            food.draw(screen)

            # 画蛇身 / 每一步+1分
            # ========== 使用新的圆形蛇身绘制方法 ==========
            draw_snake_body(screen, player_snake)
            for ai_snake in ai_snakes: draw_snake_body(screen, ai_snake)

            # 绘制UI和文字 (根据游戏状态)
            # --- 这一部分文字无论生死都会显示 ---
            show_text(screen, (50, 650), '玩家分数: ' + str(scores), (218, 195, 135), font_size=22)
            show_text(screen, (50, 740), f'撞墙次数: {wallsum} | 撞障碍物次数: {obstaclesum}',  (30, 40, 60),
                      font_size=20)

            # 同样，无论生死都计算并显示阶段和速度
            stage = 1 if scores <= score_ranges[0] else 2 if scores <= score_ranges[1] else 3
            show_text(screen, (50, 710), f'阶段: {stage} (障碍物速度: {current_obstacle_speed}x)', (0, 0, 0),
                      font_size=20)

            # --- 这一部分文字根据生死状态切换显示 ---
            if isdead:
                # 如果死亡，绘制死亡信息
                show_text(screen, (200, 300), 'YOU DEAD!', (227, 29, 18), False, 100)
                show_text(screen, (200, 430), death_reason, (200, 20, 20), False, 40)
                show_text(screen, (200, 390), 'press space to try again...', (0, 0, 22), False, 30)
            else:
                # # 如果活着，绘制游戏中需要的信息
                # # 显示当前阶段
                # stage = 1 if scores <= score_ranges[0] else 2 if scores <= score_ranges[1] else 3
                # show_text(screen, (50, 710), f'阶段: {stage} (障碍物速度: {current_obstacle_speed}x)', (144, 238, 144),
                #           font_size=20)
                # 显示特殊食物倒计时和特效描述
                if food.is_special_active:
                    # ...如果存在，就显示它的倒计时。
                    timer_text = f"Special Food: {int(food.special_remaining_time)}s"
                    show_text(screen, (SCREEN_X - 250, 10), timer_text, (255, 0, 0), font_size=24)
                    # 并且从 constant.py 字典里查出它的效果描述并显示。
                    desc = SPECIAL_FOOD_TYPES[food.special_type]["desc"]
                    show_text(screen, (SCREEN_X - 250, 40), desc, (100, 0, 200), font_size=20)

            # 如果是人机模式，额外显示AI的分数
            if settings.game_mode == 'ai':
                show_text(screen, (SCREEN_X - 250, 650), f'AI 1 分数: {ai_snakes[0].score}', (150, 170, 200),
                          font_size=22)
                show_text(screen, (SCREEN_X - 250, 680), f'AI 2 分数: {ai_snakes[1].score}', (210, 170, 160),
                          font_size=22)

        # 4. 更新屏幕显示
        pygame.display.update()
        # 5. 控制游戏帧率
        clock.tick(player_snake.get_current_speed() if not is_transitioning else 60)
        #通过它最重要的一个方法（method）—— .tick() 来实现控制速度
        #clock.tick(60) 这句代码告诉 Pygame：“请控制一下速度，确保这个 while 循环每秒钟最多只运行 60 次
    # 循环结束后
    update_high_score(scores)
    return scores


def main():
    """
    主函数：控制游戏状态流转 (已优化)
    """
    # 初始化 Pygame 库里的所有模块（比如声音、字体等）。
    pygame.init()
    # 创建游戏的主窗口，大小为 800x800，并开启 SRCAHPLA (支持透明度)。
    screen = pygame.display.set_mode((SCREEN_X, SCREEN_Y), pygame.SRCALPHA)
    # 设置窗口顶部的标题文字。
    pygame.display.set_caption('薛琦的超级贪吃蛇')

    # 初始化只执行一次
    # --- 初始化只需要创建一次的对象 ---
    # 创建“声音总管”对象。
    sound_manager = Sound(BASE_DIR)
    # 创建“游戏设置”对象。这个对象会在“结束界面”被修改，然后“传递”给下一局游戏。
    game_settings = GameSettings()
    # 初始化“当前状态”，让它指向“开始界面” (START_STATE 就是 0)。
    current_state, final_score = START_STATE, 0

    # --- 游戏状态机 (State Machine) 循环 ---
    # 这是一个永不停止的循环，它负责在三个状态之间切换。
    while True:
        # 检查当前状态是不是“开始界面”
        if current_state == START_STATE:
            # 如果是，就调用“绘制开始界面”的函数。
            # 程序会“卡”在这里，直到 start_screen 函数返回一个值
            if draw_start_screen(screen, SCREEN_X, SCREEN_Y, sound_manager) == "enter_game":
                # 如果返回的值是 "enter_game"（玩家按了回车）。
                # 就把当前状态切换为“游戏状态”。
                current_state = GAME_STATE
            else:  # 从开始界面直接退出游戏
                # 如果返回了别的值（比如 "exit"，玩家点了X）。
                # 就退出游戏。
                pygame.quit()
                sys.exit()

        # 检查当前状态是不是“游戏状态”。
        elif current_state == GAME_STATE:
            # 如果是，就调用“运行游戏循环”的函数 (我们上面分析的那个超长的函数)。
            # 程序会“卡”在这里，直到这局游戏结束（玩家死亡或按退格键）。
            game_result = run_game_loop(screen, SCREEN_X, SCREEN_Y, sound_manager, game_settings)

            # 检查游戏循环返回的是什么。
            if game_result == "restart":
                # 按空格重玩时，保持在游戏状态，直接开始新的一局
                # 如果是 "restart"（玩家按了空格）。
                # 我们使用 "continue" 关键字，它会跳过 `while True` 循环的剩余部分，
                # 立刻跳回到 `while True` 的开头。
                # 此时 `current_state` 仍然是 GAME_STATE，所以它会立刻再次进入这个 elif 块，
                # 重新调用 run_game_loop，实现“无缝重玩”。
                continue
            else:
                # 正常死亡或退出时，记录分数并进入结束/设置界面
                # 如果返回的是其他值（即玩家的最终分数）。
                # 就保存这个分数。
                final_score = game_result
                # 并且把当前状态切换为“结束界面”
                current_state = END_STATE

        # 检查当前状态是不是“结束界面”
        elif current_state == END_STATE:
            # 如果是，就调用“绘制结束界面”的函数。
            # 我们把“最终分数”传进去（用来显示），并把“当前设置”传进去（用来显示和修改）。
            # 程序会“卡”在这里，直到玩家在结束界面上做出选择。
            # 这个函数会返回两个值：玩家的选择(result)和修改后的设置(updated_settings)。
            result, updated_settings = draw_end_screen(screen, SCREEN_X, SCREEN_Y, sound_manager, final_score,game_settings)
            # 用玩家修改后的新设置，覆盖掉旧的设置
            game_settings = updated_settings

            ## --- 核心修改：修复无法进入新游戏的问题 --- ##
            if result == "back_to_start":
                # 当在结束界面点击“开始新游戏”时
                # 如果是（玩家点击了“开始新游戏”按钮）。
                # 重置分数。
                final_score = 0
                # 关键：直接把状态切换回“游戏状态”(GAME_STATE)。
                # 这样，下一次 `while True` 循环时，就会直接进入游戏，
                # 实现了“跳过开始界面，直接开始新一局”的功能。
                # 直接将状态切换到 GAME_STATE，而不是 START_STATE
                current_state = GAME_STATE
            ## --- 结束修改 --- ##


if __name__ == '__main__':
    main()